{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chapter 7: Semi-Supervised GAN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "from keras import backend as K\n",
    "\n",
    "from keras.datasets import mnist\n",
    "from keras.layers import (Activation, BatchNormalization, Concatenate, Dense,\n",
    "                          Dropout, Flatten, Input, Lambda, Reshape)\n",
    "from keras.layers.advanced_activations import LeakyReLU\n",
    "from keras.layers.convolutional import Conv2D, Conv2DTranspose\n",
    "from keras.models import Model, Sequential\n",
    "from keras.optimizers import Adam\n",
    "from keras.utils import to_categorical"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dataset:\n",
    "    def __init__(self, num_labeled):\n",
    "\n",
    "        # Number labeled examples to use for training\n",
    "        self.num_labeled = num_labeled\n",
    "\n",
    "        # Load the MNIST dataset\n",
    "        (self.x_train, self.y_train), (self.x_test,\n",
    "                                       self.y_test) = mnist.load_data()\n",
    "\n",
    "        def preprocess_imgs(x):\n",
    "            # Rescale [0, 255] grayscale pixel values to [-1, 1]\n",
    "            x = (x.astype(np.float32) - 127.5) / 127.5\n",
    "            # Expand image dimensions to width x height x channels\n",
    "            x = np.expand_dims(x, axis=3)\n",
    "            return x\n",
    "\n",
    "        def preprocess_labels(y):\n",
    "            return y.reshape(-1, 1)\n",
    "\n",
    "        # Training data\n",
    "        self.x_train = preprocess_imgs(self.x_train)\n",
    "        self.y_train = preprocess_labels(self.y_train)\n",
    "\n",
    "        # Testing data\n",
    "        self.x_test = preprocess_imgs(self.x_test)\n",
    "        self.y_test = preprocess_labels(self.y_test)\n",
    "\n",
    "    def batch_labeled(self, batch_size):\n",
    "        # Get a random batch of labeled images and their labels\n",
    "        idx = np.random.randint(0, self.num_labeled, batch_size)\n",
    "        imgs = self.x_train[idx]\n",
    "        labels = self.y_train[idx]\n",
    "        return imgs, labels\n",
    "\n",
    "    def batch_unlabeled(self, batch_size):\n",
    "        # Get a random batch of unlabeled images\n",
    "        idx = np.random.randint(self.num_labeled, self.x_train.shape[0],\n",
    "                                batch_size)\n",
    "        imgs = self.x_train[idx]\n",
    "        return imgs\n",
    "\n",
    "    def training_set(self):\n",
    "        x_train = self.x_train[range(self.num_labeled)]\n",
    "        y_train = self.y_train[range(self.num_labeled)]\n",
    "        return x_train, y_train\n",
    "\n",
    "    def test_set(self):\n",
    "        return self.x_test, self.y_test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Number of labeled examples to use (rest will be used as unlabeled)\n",
    "num_labeled = 100\n",
    "\n",
    "dataset = Dataset(num_labeled)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Semi-Supervied GAN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "img_rows = 28\n",
    "img_cols = 28\n",
    "channels = 1\n",
    "\n",
    "# Input image dimensions\n",
    "img_shape = (img_rows, img_cols, channels)\n",
    "\n",
    "# Size of the noise vector, used as input to the Generator\n",
    "z_dim = 100\n",
    "\n",
    "# Number of classes in the dataset\n",
    "num_classes = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_generator(z_dim):\n",
    "\n",
    "    model = Sequential()\n",
    "\n",
    "    # Reshape input into 7x7x256 tensor via a fully connected layer\n",
    "    model.add(Dense(256 * 7 * 7, input_dim=z_dim))\n",
    "    model.add(Reshape((7, 7, 256)))\n",
    "\n",
    "    # Transposed convolution layer, from 7x7x256 into 14x14x128 tensor\n",
    "    model.add(Conv2DTranspose(128, kernel_size=3, strides=2, padding='same'))\n",
    "\n",
    "    # Batch normalization\n",
    "    model.add(BatchNormalization())\n",
    "\n",
    "    # Leaky ReLU activation\n",
    "    model.add(LeakyReLU(alpha=0.01))\n",
    "\n",
    "    # Transposed convolution layer, from 14x14x128 to 14x14x64 tensor\n",
    "    model.add(Conv2DTranspose(64, kernel_size=3, strides=1, padding='same'))\n",
    "\n",
    "    # Batch normalization\n",
    "    model.add(BatchNormalization())\n",
    "\n",
    "    # Leaky ReLU activation\n",
    "    model.add(LeakyReLU(alpha=0.01))\n",
    "\n",
    "    # Transposed convolution layer, from 14x14x64 to 28x28x1 tensor\n",
    "    model.add(Conv2DTranspose(1, kernel_size=3, strides=2, padding='same'))\n",
    "\n",
    "    # Output layer with tanh activation\n",
    "    model.add(Activation('tanh'))\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Discriminator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_discriminator_net(img_shape):\n",
    "\n",
    "    model = Sequential()\n",
    "\n",
    "    # Convolutional layer, from 28x28x1 into 14x14x32 tensor\n",
    "    model.add(\n",
    "        Conv2D(32,\n",
    "               kernel_size=3,\n",
    "               strides=2,\n",
    "               input_shape=img_shape,\n",
    "               padding='same'))\n",
    "\n",
    "    # Leaky ReLU activation\n",
    "    model.add(LeakyReLU(alpha=0.01))\n",
    "\n",
    "    # Convolutional layer, from 14x14x32 into 7x7x64 tensor\n",
    "    model.add(\n",
    "        Conv2D(64,\n",
    "               kernel_size=3,\n",
    "               strides=2,\n",
    "               input_shape=img_shape,\n",
    "               padding='same'))\n",
    "\n",
    "    # Batch normalization\n",
    "    model.add(BatchNormalization())\n",
    "\n",
    "    # Leaky ReLU activation\n",
    "    model.add(LeakyReLU(alpha=0.01))\n",
    "\n",
    "    # Convolutional layer, from 7x7x64 tensor into 3x3x128 tensor\n",
    "    model.add(\n",
    "        Conv2D(128,\n",
    "               kernel_size=3,\n",
    "               strides=2,\n",
    "               input_shape=img_shape,\n",
    "               padding='same'))\n",
    "\n",
    "    # Batch normalization\n",
    "    model.add(BatchNormalization())\n",
    "\n",
    "    # Leaky ReLU activation\n",
    "    model.add(LeakyReLU(alpha=0.01))\n",
    "\n",
    "    # Droupout\n",
    "    model.add(Dropout(0.5))\n",
    "\n",
    "    # Flatten the tensor\n",
    "    model.add(Flatten())\n",
    "\n",
    "    # Fully connected layer with num_classes neurons\n",
    "    model.add(Dense(num_classes))\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_discriminator_supervised(discriminator_net):\n",
    "\n",
    "    model = Sequential()\n",
    "\n",
    "    model.add(discriminator_net)\n",
    "\n",
    "    # Softmax activation, giving predicted probability distribution over the real classes\n",
    "    model.add(Activation('softmax'))\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_discriminator_unsupervised(discriminator_net):\n",
    "\n",
    "    model = Sequential()\n",
    "\n",
    "    model.add(discriminator_net)\n",
    "\n",
    "    def predict(x):\n",
    "        # Transform distribution over real classes into a binary real-vs-fake probability\n",
    "        prediction = 1.0 - (1.0 /\n",
    "                            (K.sum(K.exp(x), axis=-1, keepdims=True) + 1.0))\n",
    "        return prediction\n",
    "\n",
    "    # 'Real-vs-fake' output neuron defined above\n",
    "    model.add(Lambda(predict))\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Build the Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_gan(generator, discriminator):\n",
    "\n",
    "    model = Sequential()\n",
    "\n",
    "    # Combined Generator -> Discriminator model\n",
    "    model.add(generator)\n",
    "    model.add(discriminator)\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Discriminator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Core Discriminator network:\n",
    "# These layers are shared during supervised and unsupervised training\n",
    "discriminator_net = build_discriminator_net(img_shape)\n",
    "\n",
    "# Build & compile the Discriminator for supervised training\n",
    "discriminator_supervised = build_discriminator_supervised(discriminator_net)\n",
    "discriminator_supervised.compile(loss='categorical_crossentropy',\n",
    "                                 metrics=['accuracy'],\n",
    "                                 optimizer=Adam())\n",
    "\n",
    "# Build & compile the Discriminator for unsupervised training\n",
    "discriminator_unsupervised = build_discriminator_unsupervised(discriminator_net)\n",
    "discriminator_unsupervised.compile(loss='binary_crossentropy',\n",
    "                                   optimizer=Adam())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Generator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Build the Generator\n",
    "generator = build_generator(z_dim)\n",
    "\n",
    "# Keep Discriminator’s parameters constant for Generator training\n",
    "discriminator_unsupervised.trainable = False\n",
    "\n",
    "# Build and compile GAN model with fixed Discriminator to train the Generator\n",
    "# Note that we are using the Discriminator version with unsupervised output\n",
    "gan = build_gan(generator, discriminator_unsupervised)\n",
    "gan.compile(loss='binary_crossentropy', optimizer=Adam())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "supervised_losses = []\n",
    "iteration_checkpoints = []\n",
    "\n",
    "\n",
    "def train(iterations, batch_size, sample_interval):\n",
    "\n",
    "    # Labels for real images: all ones\n",
    "    real = np.ones((batch_size, 1))\n",
    "\n",
    "    # Labels for fake images: all zeros\n",
    "    fake = np.zeros((batch_size, 1))\n",
    "\n",
    "    for iteration in range(iterations):\n",
    "\n",
    "        # -------------------------\n",
    "        #  Train the Discriminator\n",
    "        # -------------------------\n",
    "\n",
    "        # Get labeled examples\n",
    "        imgs, labels = dataset.batch_labeled(batch_size)\n",
    "\n",
    "        # One-hot encode labels\n",
    "        labels = to_categorical(labels, num_classes=num_classes)\n",
    "\n",
    "        # Get unlabeled examples\n",
    "        imgs_unlabeled = dataset.batch_unlabeled(batch_size)\n",
    "\n",
    "        # Generate a batch of fake images\n",
    "        z = np.random.normal(0, 1, (batch_size, z_dim))\n",
    "        gen_imgs = generator.predict(z)\n",
    "\n",
    "        # Train on real labeled examples\n",
    "        d_loss_supervised, accuracy = discriminator_supervised.train_on_batch(imgs, labels)\n",
    "\n",
    "        # Train on real unlabeled examples\n",
    "        d_loss_real = discriminator_unsupervised.train_on_batch(\n",
    "            imgs_unlabeled, real)\n",
    "\n",
    "        # Train on fake examples\n",
    "        d_loss_fake = discriminator_unsupervised.train_on_batch(gen_imgs, fake)\n",
    "\n",
    "        d_loss_unsupervised = 0.5 * np.add(d_loss_real, d_loss_fake)\n",
    "\n",
    "        # ---------------------\n",
    "        #  Train the Generator\n",
    "        # ---------------------\n",
    "\n",
    "        # Generate a batch of fake images\n",
    "        z = np.random.normal(0, 1, (batch_size, z_dim))\n",
    "        gen_imgs = generator.predict(z)\n",
    "\n",
    "        # Train Generator\n",
    "        g_loss = gan.train_on_batch(z, np.ones((batch_size, 1)))\n",
    "\n",
    "        if (iteration + 1) % sample_interval == 0:\n",
    "\n",
    "            # Save Discriminator supervised classification loss to be plotted after training\n",
    "            supervised_losses.append(d_loss_supervised)\n",
    "            iteration_checkpoints.append(iteration + 1)\n",
    "\n",
    "            # Output training progress\n",
    "            print(\n",
    "                \"%d [D loss supervised: %.4f, acc.: %.2f%%] [D loss unsupervised: %.4f] [G loss: %f]\"\n",
    "                % (iteration + 1, d_loss_supervised, 100 * accuracy,\n",
    "                   d_loss_unsupervised, g_loss))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train the Model and Inspect Output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the `'Discrepancy between trainable weights and collected trainable'` warning from Keras is expected. It is by design: The Generator's trainable parameters are intentionally held constant during Discriminator training, and vice versa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.6/site-packages/keras/engine/training.py:478: UserWarning: Discrepancy between trainable weights and collected trainable weights, did you set `model.trainable` without calling `model.compile` after ?\n",
      "  'Discrepancy between trainable weights and collected trainable'\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "800 [D loss supervised: 0.0023, acc.: 100.00%] [D loss unsupervised: 0.1624] [G loss: 3.555443]\n",
      "1600 [D loss supervised: 0.0003, acc.: 100.00%] [D loss unsupervised: 0.2607] [G loss: 3.122660]\n",
      "2400 [D loss supervised: 0.0005, acc.: 100.00%] [D loss unsupervised: 0.3824] [G loss: 3.318452]\n",
      "3200 [D loss supervised: 0.0002, acc.: 100.00%] [D loss unsupervised: 0.2366] [G loss: 4.759000]\n",
      "4000 [D loss supervised: 0.0001, acc.: 100.00%] [D loss unsupervised: 0.2454] [G loss: 3.951427]\n",
      "4800 [D loss supervised: 0.0012, acc.: 100.00%] [D loss unsupervised: 0.5676] [G loss: 3.136265]\n",
      "5600 [D loss supervised: 0.0003, acc.: 100.00%] [D loss unsupervised: 0.4493] [G loss: 5.773734]\n",
      "6400 [D loss supervised: 0.0004, acc.: 100.00%] [D loss unsupervised: 0.4465] [G loss: 5.712636]\n",
      "7200 [D loss supervised: 0.0004, acc.: 100.00%] [D loss unsupervised: 0.3834] [G loss: 5.106216]\n",
      "8000 [D loss supervised: 0.0003, acc.: 100.00%] [D loss unsupervised: 0.2398] [G loss: 3.324689]\n"
     ]
    }
   ],
   "source": [
    "# Set hyperparameters\n",
    "iterations = 8000\n",
    "batch_size = 32\n",
    "sample_interval = 800\n",
    "\n",
    "# Train the SGAN for the specified number of iterations\n",
    "train(iterations, batch_size, sample_interval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x7f20567307f0>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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5jLue2uZ1zFERERERDpM7969eeihh7jvvvtwzvHOO+9w1llnAfDuu+9SUFBAQUEB48ePZ9u2bQDcfvvtjBkzhnHjxnHjjTcCcPzxx3PNNdfg8/n405/+9IWjd8cffzzXXnstPp+PESNG8Mknn3DuuecyZMgQbrrppt1ZunbtCsA777zD8ccfz9SpUxk+fDgXXXQRzjkAbr31ViZOnMjo0aO54oorcM4xY8YM/H4/F110EQUFBezYsYM333yT8ePHM2bMGL71rW+xc+dOAPr3788NN9zAhAkTKC4u3uf3Ze7cuRx55JGMHTuWKVOmsGXLFgDuvfdeRo4cydixYzn//PP3+31qLzriGGE+3biND5Zv4vKvDiQ5IfL/8iAiIiIi0e2WFxaxuLymXbc5Mqc7vzp71EGtM3DgQJqbm6moqPjC9DvvvJP777+fo48+mtraWlJSUnjllVd4/vnn+eijj0hNTWXz5s27l29oaMDv9wPB0z5bSkpKwu/386c//YlJkyZRUlJCRkYGgwYN4tprryUzM/MLy8+ZM4dFixaRk5PD0UcfzQcffMAxxxzDVVddxS9/+UsALr74Yl588UWmTp3Kfffdx5133onP56O+vp7LLruMN998k6FDh3LJJZfwl7/8hWuuuQaAzMxMZs+evd/vySWXXMKf//xnjjvuOH75y19yyy23cM899/D73/+eVatWkZyczNatW/f5fWpPOuIYYYoK89ha18ibSyoOvLCIiIiISCd39NFHc91113HvvfeydetWEhISeOONN/jmN79JamoqABkZGbuX//rXv77PbZ1zzjkAjBkzhlGjRpGdnU1ycjIDBw5k3bp1ey1/+OGHk5eXR1xcHAUFBaxevRqAt99+myOOOIIxY8bw1ltvsWjRor3WXbp0KQMGDGDo0KEAXHrppbz33nttyglQXV3N1q1bOe644/Zaf+zYsVx00UX84x//ICEhYZ/fp/akI44R5tghWfTtnsJ0/zrOGJPtdRwRERER6eQO9shguKxcuZL4+Hh69+7NkiVLdk+/8cYbOfPMM3n55Zc5+uijee211/a7nbS0tH3OS05OBiAuLm73812vm5qa9rk8BAfzaWpqor6+nu9///v4/X7y8/O5+eabD+k2F/vLeSAvvfQS7733Hi+88AK33XYbCxYsaPX7NHz48EN+jz3piGOEiY8zzivM5b3PKtlQfej3WRERERERiRaVlZVceeWVXHXVVXvdNmLFihWMGTOGG264gYkTJ/Lpp59y8skn88gjj1BXVwfwhVNVw21XSezVqxe1tbXMmDFj97xu3brtvrZw2LBhrF69muXLlwPwxBNP7D562Bbp6en07NmT999//wvrBwIB1q1bxwknnMDtt99OdXU1tbW1rX6f2pOOOEagosJ87n97BTNnl/KDEwZ7HUdEREREpN3t2LGDgoICGhsbSUhI4OKLL+a6667ba7l77rmHt99+m7i4OEaNGsXpp59OcnIyc+fOxefzkZSUxBlnnMFvf/vbDsndo0cPLr/8ckaPHk3fvn2ZOHHi7nmXXXYZV155JV26dOHDDz/kkUceoaioiKamJiZOnHjQo7w+9thjXHnlldTV1TFw4EAeeeQRmpub+cY3vkF1dTXOOa6++mp69OjBL37xi72+T+3Jdo0MFIt8Pp/bdeFspJn21w+prN3JW9cfp5uzioiIiEi7WrJkCSNGjPA6hnSw1n7uZlbinDvgvU90qmqEKvLlsWrTdkrWbPE6ioiIiIiIxDgVxwh1xphsUpPime7fe3QnERERERGRjqTiGKHSkhM4a2w2L81fz/ade4/wJCIiIiIi0lFUHCNYkS+f7Q3NvLxgvddRRERERKSTieWxTmLRl/15qzhGMF+/ngzolUZxSanXUURERESkE0lJSaGqqkrlMUY456iqqiIlJeWQt6HbcUQwM2NqYR5/eG0pqzdtp3+vQ79JqIiIiIjILnl5eZSWllJZWel1FOkgKSkp5OXlHfL6Ko4R7rwJefzx30uZUVLKj04d5nUcEREREekEEhMTGTBggNcxJIroVNUI1zc9ha8OzWJGSSnNAZ1KICIiIiIiHU/FMQoUFeazoaae/yzf5HUUERERERGJQSqOUeCkkb3pkZqoezqKiIiIiIgnVByjQHJCPJMLcnl90Ua21jV4HUdERERERGKMimOUKPLl0dAc4Pm55V5HERERERGRGKPiGCVG5aQzMrs7xSU6XVVERERERDqWimMUmebLY2FZDYvLa7yOIiIiIiIiMUTFMYpMKsglKT5ORx1FRERERKRDqThGkZ5pSZw8sg/PzSmjoSngdRwREREREYkRKo5RZqovjy11jby5ZKPXUUREREREJEaoOEaZrw7Jom/3FIpLSr2OIiIiIiIiMULFMcrExxnnTsjlnaUVbKyp9zqOiIiIiIjEABXHKFTkyyfg4JnZZV5HERERERGRGKDiGIUG9EpjYv+eFPvX4ZzzOo6IiIiIiHRyKo5RqsiXz8pN25m9dovXUUREREREpJMLa3E0s9PMbKmZLTezG1uZn2xmT4fmf2Rm/VvM+2lo+lIzOzU0Ld/M3jazxWa2yMz+r8XyGWb2upktC33tGc5989qZY7JJTYpn+icaJEdERERERMIrbMXRzOKB+4HTgZHABWY2co/Fvg1scc4NBu4Gbg+tOxI4HxgFnAY8ENpeE3C9c24kcCTwgxbbvBF40zk3BHgz9LrTSktO4Mwx2bw4v5y6hiav44iIiIiISCcWziOOhwPLnXMrnXMNwFPApD2WmQQ8Fno+A/iamVlo+lPOuZ3OuVXAcuBw59x659xsAOfcNmAJkNvKth4DJodpvyJGkS+f7Q3NvLxgg9dRRERERESkEwtnccwF1rV4XcrnJW+vZZxzTUA1kNmWdUOntY4HPgpN6uOcWx96vgHo01ooM7vCzPxm5q+srDy4PYowE/v3pH9mKsX+dQdeWERERERE5BBF5eA4ZtYVmAlc45yr2XO+Cw412upwo865h5xzPuecLysrK8xJw8vMKPLl89Gqzayp2u51HBERERER6aTCWRzLgPwWr/NC01pdxswSgHSgan/rmlkiwdL4T+fcMy2W2Whm2aFlsoGKdtuTCHbuhFziDGaUaJAcEREREREJj3AWx0+AIWY2wMySCA52M2uPZWYBl4aeTwXeCh0tnAWcHxp1dQAwBPg4dP3j34Alzrm79rOtS4Hn232PIlB2eheOHZLFzJJSmgO6p6OIiIiIiLS/sBXH0DWLVwGvERzEZrpzbpGZ3Wpm54QW+xuQaWbLgesIjYTqnFsETAcWA68CP3DONQNHAxcDJ5rZ3NDjjNC2fg+cbGbLgJNCr2NCkS+P8up6Pli+yesoIiIiIiLSCVnwAF9s8vl8zu/3ex3jS9vZ1MwRv32TY4dk8ecLxnsdR0REREREooSZlTjnfAdaLioHx5EvSk6IZ9K4HF5btIHqukav44iIiIiISCej4thJFPnyaWgKMGvenuMPiYiIiIiIfDkqjp3E6Nx0RmR3Z7pfo6uKiIiIiEj7UnHsRKb58lhQVs2S9Xvd2lJEREREROSQqTh2IpMKckmMN4p11FFERERERNqRimMnkpGWxMkj+/Dc3DIamgJexxERERERkU5CxbGTKSrMZ/P2Bt76dKPXUUREREREpJNQcexkjh3Siz7dk3W6qoiIiIiItBsVx04mIT6Ocyfk8fbSCipq6r2OIyIiIiIinYCKYydUVJhHwMEzc3RPRxERERER+fJUHDuhgVld8fXrSbF/Hc45r+OIiIiIiEiUU3HspIp8eayo3M7stVu9jiIiIiIiIlFOxbGTOnNsDl0S45lRss7rKCIiIiIiEuVUHDuprskJnDEmmxfmraeuocnrOCIiIiIiEsVUHDuxab48anc28erCDV5HERERERGRKKbi2IkdPiCDfpmpTPfrdFURERERETl0Ko6dmJlRVJjH/1ZuZm1VnddxREREREQkSqk4dnLnTsjDDA2SIyIiIiIih0zFsZPL6dGFY4dkMaOklOaA7ukoIiIiIiIHT8UxBhQV5lFeXc9/V2zyOoqIiIiIiEQhFccYcPLIPqR3SaTYX+p1FBERERERiUIqjjEgJTGeSQU5vLpoA9V1jV7HERERERGRKKPiGCOm+fJpaAowa36511FERERERCTKqDjGiFE53RnetxvFuqejiIiIiIgcJBXHGGFmFPnymV9azdIN27yOIyIiIiIiUUTFMYZMLsghMd501FFERERERA6KimMMyeyazNeG9+HZOWU0Nge8jiMiIiIiIlFCxTHGTJuYR9X2Bt76tMLrKCIiIiIiEiVUHGPMV4dk0btbsk5XFRERERGRNlNxjDEJ8XGcOyGPt5dWUrGt3us4IiIiIiISBVQcY1CRL4/mgOPZ2WVeRxERERERkSig4hiDBmV1pbBfT4pLSnHOeR1HREREREQinIpjjCoqzGN5RS1z1m31OoqIiIiIiEQ4FccYdebYbLokxlPsL/U6ioiIiIiIRDgVxxjVLSWR08f05YV55exoaPY6joiIiIiIRDAVxxg2zZdP7c4mXl203usoIiIiIiISwVQcY9gRAzI4LCOV6Z/odFUREREREdk3FccYZmYUFebx4coq1m2u8zqOiIiIiIhEKBXHGHdeYR5mUFyio44iIiIiItI6FccYl9OjC8cM7sXMklICAd3TUURERERE9qbiKBT58inbuoMPV1Z5HUVERERERCKQiqNwysg+dE9JYLp/nddRREREREQkAqk4CimJ8UwqyOXVhRuo3tHodRwRERER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\n",
      "text/plain": [
       "<Figure size 1080x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "losses = np.array(supervised_losses)\n",
    "\n",
    "# Plot Discriminator supervised loss\n",
    "plt.figure(figsize=(15, 5))\n",
    "plt.plot(iteration_checkpoints, losses, label=\"Discriminator loss\")\n",
    "\n",
    "plt.xticks(iteration_checkpoints, rotation=90)\n",
    "\n",
    "plt.title(\"Discriminator – Supervised Loss\")\n",
    "plt.xlabel(\"Iteration\")\n",
    "plt.ylabel(\"Loss\")\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## SGAN Classifier – Training and Test Accuracy "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100/100 [==============================] - 0s 2ms/step\n",
      "Training Accuracy: 100.00%\n"
     ]
    }
   ],
   "source": [
    "x, y = dataset.training_set()\n",
    "y = to_categorical(y, num_classes=num_classes)\n",
    "\n",
    "# Compute classification accuracy on the training set\n",
    "_, accuracy = discriminator_supervised.evaluate(x, y)\n",
    "print(\"Training Accuracy: %.2f%%\" % (100 * accuracy))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10000/10000 [==============================] - 1s 74us/step\n",
      "Test Accuracy: 89.41%\n"
     ]
    }
   ],
   "source": [
    "x, y = dataset.test_set()\n",
    "y = to_categorical(y, num_classes=num_classes)\n",
    "\n",
    "# Compute classification accuracy on the test set\n",
    "_, accuracy = discriminator_supervised.evaluate(x, y)\n",
    "print(\"Test Accuracy: %.2f%%\" % (100 * accuracy))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Fully-Supervised Classifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Fully supervised classifier with the same network architecture as the SGAN Discriminator\n",
    "mnist_classifier = build_discriminator_supervised(build_discriminator_net(img_shape))\n",
    "mnist_classifier.compile(loss='categorical_crossentropy',\n",
    "                         metrics=['accuracy'],\n",
    "                         optimizer=Adam())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/30\n",
      "100/100 [==============================] - 1s 9ms/step - loss: 2.8591 - acc: 0.1400\n",
      "Epoch 2/30\n",
      "100/100 [==============================] - 0s 327us/step - loss: 1.0608 - acc: 0.6600\n",
      "Epoch 3/30\n",
      "100/100 [==============================] - 0s 289us/step - loss: 0.7125 - acc: 0.7800\n",
      "Epoch 4/30\n",
      "100/100 [==============================] - 0s 296us/step - loss: 0.3955 - acc: 0.8900\n",
      "Epoch 5/30\n",
      "100/100 [==============================] - 0s 300us/step - loss: 0.2517 - acc: 0.9600\n",
      "Epoch 6/30\n",
      "100/100 [==============================] - 0s 291us/step - loss: 0.2174 - acc: 0.9600\n",
      "Epoch 7/30\n",
      "100/100 [==============================] - 0s 302us/step - loss: 0.1430 - acc: 0.9800\n",
      "Epoch 8/30\n",
      "100/100 [==============================] - 0s 295us/step - loss: 0.0915 - acc: 0.9900\n",
      "Epoch 9/30\n",
      "100/100 [==============================] - 0s 292us/step - loss: 0.0753 - acc: 0.9900\n",
      "Epoch 10/30\n",
      "100/100 [==============================] - 0s 294us/step - loss: 0.0693 - acc: 1.0000\n",
      "Epoch 11/30\n",
      "100/100 [==============================] - 0s 301us/step - loss: 0.0659 - acc: 0.9900\n",
      "Epoch 12/30\n",
      "100/100 [==============================] - 0s 299us/step - loss: 0.0351 - acc: 1.0000\n",
      "Epoch 13/30\n",
      "100/100 [==============================] - 0s 308us/step - loss: 0.0403 - acc: 1.0000\n",
      "Epoch 14/30\n",
      "100/100 [==============================] - 0s 302us/step - loss: 0.0233 - acc: 1.0000\n",
      "Epoch 15/30\n",
      "100/100 [==============================] - 0s 296us/step - loss: 0.0295 - acc: 1.0000\n",
      "Epoch 16/30\n",
      "100/100 [==============================] - 0s 303us/step - loss: 0.0211 - acc: 1.0000\n",
      "Epoch 17/30\n",
      "100/100 [==============================] - 0s 301us/step - loss: 0.0226 - acc: 1.0000\n",
      "Epoch 18/30\n",
      "100/100 [==============================] - 0s 295us/step - loss: 0.0173 - acc: 1.0000\n",
      "Epoch 19/30\n",
      "100/100 [==============================] - 0s 304us/step - loss: 0.0176 - acc: 1.0000\n",
      "Epoch 20/30\n",
      "100/100 [==============================] - 0s 301us/step - loss: 0.0130 - acc: 1.0000\n",
      "Epoch 21/30\n",
      "100/100 [==============================] - 0s 290us/step - loss: 0.0149 - acc: 1.0000\n",
      "Epoch 22/30\n",
      "100/100 [==============================] - 0s 299us/step - loss: 0.0087 - acc: 1.0000\n",
      "Epoch 23/30\n",
      "100/100 [==============================] - 0s 292us/step - loss: 0.0117 - acc: 1.0000\n",
      "Epoch 24/30\n",
      "100/100 [==============================] - 0s 295us/step - loss: 0.0115 - acc: 1.0000\n",
      "Epoch 25/30\n",
      "100/100 [==============================] - 0s 340us/step - loss: 0.0078 - acc: 1.0000\n",
      "Epoch 26/30\n",
      "100/100 [==============================] - 0s 302us/step - loss: 0.0073 - acc: 1.0000\n",
      "Epoch 27/30\n",
      "100/100 [==============================] - 0s 295us/step - loss: 0.0056 - acc: 1.0000\n",
      "Epoch 28/30\n",
      "100/100 [==============================] - 0s 291us/step - loss: 0.0063 - acc: 1.0000\n",
      "Epoch 29/30\n",
      "100/100 [==============================] - 0s 286us/step - loss: 0.0063 - acc: 1.0000\n",
      "Epoch 30/30\n",
      "100/100 [==============================] - 0s 291us/step - loss: 0.0089 - acc: 1.0000\n"
     ]
    }
   ],
   "source": [
    "imgs, labels = dataset.training_set()\n",
    "\n",
    "# One-hot encode labels\n",
    "labels = to_categorical(labels, num_classes=num_classes)\n",
    "\n",
    "# Train the classifier\n",
    "training = mnist_classifier.fit(x=imgs,\n",
    "                                y=labels,\n",
    "                                batch_size=32,\n",
    "                                epochs=30,\n",
    "                                verbose=1)\n",
    "losses = training.history['loss']\n",
    "accuracies = training.history['acc']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x7f2054523da0>"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot classification loss\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(np.array(losses), label=\"Loss\")\n",
    "plt.title(\"Classification Loss\")\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x7f20544fe160>"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot classification accuracy\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(np.array(accuracies), label=\"Accuracy\")\n",
    "plt.title(\"Classification Accuracy\")\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100/100 [==============================] - 0s 2ms/step\n",
      "Training Accuracy: 100.00%\n"
     ]
    }
   ],
   "source": [
    "x, y = dataset.training_set()\n",
    "y = to_categorical(y, num_classes=num_classes)\n",
    "\n",
    "# Compute classification accuracy on the training set\n",
    "_, accuracy = mnist_classifier.evaluate(x, y)\n",
    "print(\"Training Accuracy: %.2f%%\" % (100 * accuracy))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10000/10000 [==============================] - 1s 75us/step\n",
      "Test Accuracy: 69.22%\n"
     ]
    }
   ],
   "source": [
    "x, y = dataset.test_set()\n",
    "y = to_categorical(y, num_classes=num_classes)\n",
    "\n",
    "# Compute classification accuracy on the test set\n",
    "_, accuracy = mnist_classifier.evaluate(x, y)\n",
    "print(\"Test Accuracy: %.2f%%\" % (100 * accuracy))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
